import java.util.ArrayList;
import java.util.HashMap;

import static java.lang.System.err;
import static java.lang.System.out;

public abstract class IterDeepDrawFix extends ComputerPlayer {

    protected static final Integer ZERO = Integer.valueOf(0);
    protected static final Integer ONE  = Integer.valueOf(1);

    private static long MPS = 1000; // Milliseconds Per Second
    private static long MAX_TIME = 300 * MPS;

    protected HashMap<Board, Integer> boardHistory;
    protected Board[] drawBoards = new Board[300];
    protected int drawBoardsSize = 0;


    public IterDeepDrawFix(boolean isBlack, Eval evaluator, int maxDepth) {
        super(isBlack, evaluator, maxDepth);
        boardHistory = new HashMap<Board, Integer>();
        printBestMoveValue = true;
    }


    public abstract Board findMove(Board board, ArrayList<Board> moves,
                                   int depth);


    @Override
    public Action getMove(Board board, int moveNbr) {

        nbrNodesVisited = 0;

        long startTime = System.currentTimeMillis();

        assert board.blackToMove == isBlack;

        Board bestMove = null;

        /*
        switch(moveNbr){
        case 1:
            bestMove = new Board(board);
            if(isBlack){bestMove.makeMove(new int[] {11,15});}
            else{bestMove.makeMove(new int[] {22,18});}
            bestMove.blackToMove = !board.blackToMove; break;
        case 2:
            bestMove = openings.get(board); 
            if(bestMove != null) break;
        default:
        */
            ArrayList<Board> moves = board.generateMoves();

            nbrMovesCount[moves.size()]++;

            int nbrBlacks = BitUtils.countBits(board.black);
            int nbrWhites = BitUtils.countBits(board.white);
            int nbrCheckers = nbrBlacks + nbrWhites;
            int estMovesLeft = (50 * nbrCheckers) / 24;
            long timeLeft = MAX_TIME - spentTime;
            long maxMoveTime = (timeLeft * 2) / (estMovesLeft * 4);

            int maxPly = searchDepth;
            if (nbrCheckers >= 15) {
                maxPly = 12;
            }

            out.println("Estimated # moves left: " + estMovesLeft);
            float timeLeftF = timeLeft / 1000.0F;
            out.printf("Time left: %.2f s.\n", timeLeftF);
            float maxMoveTimeF = maxMoveTime / 1000.0F;
            out.printf("Max time for this move: %.2f s.\n", maxMoveTimeF);

            // Iterative deepening
            int depth = 2;
            long thisMoveTime = 0L;
            do {
                out.println("At ply " + depth + ". ");
                bestMove = findMove(board, moves, depth);
                depth += 2;
                long soFar = System.currentTimeMillis();
                thisMoveTime = soFar - startTime;
                float thisMoveTimeF = thisMoveTime / 1000.0F;
                out.printf("This move yet: %.2f s.\n", thisMoveTimeF);
            } while ((thisMoveTime < maxMoveTime) && depth <= maxPly);

            int nBlack = BitUtils.countBits( bestMove.black );
            int nWhite = BitUtils.countBits( bestMove.white );

            // This board can only occur again if there are at least 1 king of each color
            // and is probably never repeated if > 5 pieces of each
            if( (board.black & board.kings)!=0 && (board.white & board.kings)!=0
                && (nBlack < 6)  && (nWhite < 6) ) {
                int nOldPieces = BitUtils.countBits( board.black | board.white );
                int nNewPieces = BitUtils.countBits( bestMove.black | bestMove.white );

                boolean piecesTaken = (nOldPieces - nNewPieces != 0);
                int oldNonKings = ( (board.black | board.white) & ~board.kings );
                int newNonKings = ( (bestMove.black | bestMove.white) & ~bestMove.kings );
                boolean nonKingMoved = (oldNonKings != newNonKings);

                // If this move means no prev. board will come up again
                // - clear the board history (no risk for draws among prev. boards)
                if(piecesTaken || nonKingMoved) {
                boardHistory.clear();
                drawBoardsSize = 0;
                }

                // Add/increment count of the current move to the board history
                Integer occurrences = boardHistory.get(bestMove);
                if( occurrences != null ) {
                int o = occurrences + 1;
                //out.println("Put board in boardHistory with a count of " + o);
                //GameEngine.printBoard(bestMove);
                boardHistory.put(new Board(bestMove), o);
                if(o == 2) {
                    drawBoards[drawBoardsSize] = new Board(bestMove);
                    drawBoardsSize++;
                }
                }
                else {
                //out.println("Put a new board in boardHistory with a count of 1");
                boardHistory.put(new Board(bestMove), ONE);
                }
            }
            /*
            break;
        }
            */

        // Print the move
        int[] myMove;
        if (bestMove != null) {
            myMove = movePathFromBoards(board, bestMove);
            out.print("My move is " + moveNbr + ": " + myMove[0]);
            for (int i = 1; i < myMove.length; i++) {
                out.print(" - " + myMove[i]);
            }
            out.println();
        } else {
            out.println(color + " couldn't find any available moves.");
            myMove = new int[0];
        }

        // Add time spent and print the time
        long endTime = System.currentTimeMillis();
        long thisMove = endTime - startTime;
        spentTime += thisMove;
        float thisMoveTotalTime = thisMove / 1000.0F;
        float totalTime = spentTime / 1000.0F;
        out.printf("This move took: %.2f s.\n", thisMoveTotalTime);
        out.printf("Total spent time: %.2f s.\n", totalTime);

        // Print nodes per second
        out.println();
        out.printf("During this move the program visited %d nodes.\n",
                   nbrNodesVisited);
        if (thisMoveTotalTime > 0.0) {
            double nodesPerSecond =
                ((double) nbrNodesVisited) / thisMoveTotalTime;
            out.printf("During this move the program visited: %.0f nodes"
                       + " / second.\n", nodesPerSecond);
        }

        // Print move count
        out.println();
        out.println("Stats for number of moves for a board during this move:");
        printStats(nbrMovesCount);

        // Print depth count
        out.println();
        out.println("Stats for depth reached when search stops during this move:");
        printStats(depthCount);

        return new Action(myMove, spentTime);
    }
}
